#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>

//#include <netpacket/packet.h>
//#include <net/ethernet.h>
#include <poll.h>
#include <ifaddrs.h>
#include <linux/if_arp.h>

struct arp_packet_t {
  __u16 htype;
  __u16 ptype;
  __u8  hlen;
  __u8  plen;  
  __u16 oper;
  __u8  sha[6];
  __u8  spa[4];
  __u8  tha[6];
  __u8  tpa[4];
};

__u8 target_ip[4];

struct ifaddrs *if_head;

int send_arp(int sock, struct arp_packet_t *arp_packet, struct sockaddr_ll *addr) {
   errno = 0;
   sendto(sock, arp_packet, sizeof(struct arp_packet_t), 
          0, (struct sockaddr*) addr, sizeof(struct sockaddr_ll));

   if (errno) {
      perror("Error sending arp request:");
      return -1;
   }
   errno = 0;
   return 0;
}

int recv_arp(int sock, struct arp_packet_t *arp_packet) {
   errno = 0;
   struct pollfd pfd;
   int ret;  

   pfd.fd = sock;
   pfd.events = POLLIN;
   ret = poll(&pfd, 1, 5000);
   if (ret <= 0)
      return ret;

   recv(sock, arp_packet, sizeof(struct arp_packet_t), 0);
   if (errno)
      perror("Error recieving arp request:");
   errno = 0;
   return 0;
}

int main(int argc, char** argv) {
   int ret = 0;
   if (argc < 2) {
      printf("Program sends ARP request for specified IP address on interface specified by name\n");
      printf("args: [ip address] [interface name]\n");
      printf("example: myarp 192.168.1.1 wlan0\n");
      return -1;
   }
   errno = 0;
   in_addr_t target_ip1 = inet_addr(argv[1]);
   if (errno || !target_ip1) {
      printf("ERROR: invalid ip\n");
      return -1;
   }
   memcpy(target_ip, &target_ip1 ,4);
   char* if_name = argv[2];
   errno = 0;

   getifaddrs(&if_head);

   struct ifaddrs *sender_ifaddrs = if_head;
   struct sockaddr_in *sender_addrs_in = 0;
   struct sockaddr_ll *sender_addrs_ll = 0;

   int i = 0;
   while(sender_ifaddrs) {
      if (!strcmp(if_name, sender_ifaddrs->ifa_name)) {
         if (sender_ifaddrs->ifa_addr->sa_family == AF_PACKET) {
            sender_addrs_ll = (struct sockaddr_ll*)sender_ifaddrs->ifa_addr;
            sender_addrs_ll->sll_protocol = htons(ETH_P_ARP);
         } else if (sender_ifaddrs->ifa_addr->sa_family == AF_INET) {
            sender_addrs_in = (struct sockaddr_in*)sender_ifaddrs->ifa_addr;
         }
      }
      sender_ifaddrs = sender_ifaddrs->ifa_next;
      if (!sender_ifaddrs)
         break;
   }

   if(!sender_addrs_in || !sender_addrs_ll) {
      printf("Couldn't find interface ip or mac");
      return -1;
   }

   errno = 0;
   int sock = socket(AF_PACKET, SOCK_DGRAM, ETH_P_ARP);
   if (errno) {
      perror("Error opening socket: ");
      return -1;
   }

   errno = 0;
   ret = bind(sock, (struct sockaddr*)sender_addrs_ll, sizeof(struct sockaddr_ll));
   if(ret) {
      perror("Error binding socket: ");
      return -1;
   }
   errno = 0;
   
   struct sockaddr_ll mac_broadcast;
   mac_broadcast.sll_family = AF_PACKET;
   mac_broadcast.sll_protocol = htons(ETH_P_ARP);
   mac_broadcast.sll_ifindex = sender_addrs_ll->sll_ifindex;
   mac_broadcast.sll_hatype = ARPHRD_ETHER;
   mac_broadcast.sll_pkttype = PACKET_BROADCAST;
   mac_broadcast.sll_halen = ETH_ALEN;
   memset(mac_broadcast.sll_addr, 0xFF, ETH_ALEN);

   struct arp_packet_t arp_req;
   arp_req.htype = htons(0x0001);
   arp_req.ptype = htons(0x0800);
   arp_req.hlen  = ETH_ALEN;
   arp_req.plen  = 4; //IPv4 addr len
   arp_req.oper = htons(1);
   memcpy(arp_req.sha, sender_addrs_ll->sll_addr, ETH_ALEN);
   memcpy(arp_req.spa, &sender_addrs_in->sin_addr, 4);
   memset(arp_req.tha, 0, ETH_ALEN);
   memcpy(arp_req.tpa, &target_ip, 4);
   
   struct arp_packet_t arp_resp;
   send_arp(sock, &arp_req, &mac_broadcast);
   i = 0;
   if (recv_arp(sock, &arp_resp)) {
      printf("ARP request timeout\n");
   } else {
      if (arp_resp.oper == ntohs(2) && 
            arp_resp.spa[0] == target_ip[0] &&
            arp_resp.spa[1] == target_ip[1] &&
            arp_resp.spa[2] == target_ip[2] &&
            arp_resp.spa[3] == target_ip[3]) {
         printf("IP: %d.%d.%d.%d MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", 
               arp_resp.spa[0], 
               arp_resp.spa[1], 
               arp_resp.spa[2], 
               arp_resp.spa[3], 
               arp_resp.sha[0], 
               arp_resp.sha[1], 
               arp_resp.sha[2], 
               arp_resp.sha[3], 
               arp_resp.sha[4], 
               arp_resp.sha[5]);
      } else {
         printf("Invlid ARP recieved, maybe there is no such on host your local network\n");
         printf("or another arp request reached your interface quicker\n");
      }
   }

   close(sock);
   freeifaddrs(if_head);
   return 0;
}
